﻿using System;
using System.Collections.Generic;
using System.Linq;
using LiteDB;
using Newtonsoft.Json;
using NLog;
using SmartQuant;

namespace QuantBox
{
    internal enum ProviderSettingsType
    {
        TradingDay = 1
    }

    internal class DatabaseOrderServer : OrderServer
    {
        #region Settings
        public class SettingsManager
        {
            private readonly ILiteCollection<BsonDocument> _collection;

            public SettingsManager(ILiteCollection<BsonDocument> collection)
            {
                _collection = collection;
            }

            public void Set(ProviderSettingsType type, bool value)
            {
                var doc = new BsonDocument
                {
                    ["_id"] = (int)type,
                    ["value"] = value
                };
                _collection.Upsert(doc);
            }
            public bool GetAsBoolean(ProviderSettingsType type)
            {
                var data = _collection.FindById((int)type);
                return data != null && data["value"].AsBoolean;
            }
            public void Set(ProviderSettingsType type, byte[] value)
            {
                var doc = new BsonDocument
                {
                    ["_id"] = (int)type,
                    ["value"] = value
                };
                _collection.Upsert(doc);
            }
            public byte[]? GetAsBinary(ProviderSettingsType type)
            {
                var data = _collection.FindById((int)type);
                return data != null ? data["value"].AsBinary : default;
            }
            public void Set(ProviderSettingsType type, DateTime value)
            {
                var doc = new BsonDocument
                {
                    ["_id"] = (int)type,
                    ["value"] = value
                };
                _collection.Upsert(doc);
            }
            public DateTime GetAsDateTime(ProviderSettingsType type)
            {
                var data = _collection.FindById((int)type);
                return data != null ? data["value"].AsDateTime : default;
            }
            public void Set(ProviderSettingsType type, decimal value)
            {
                var doc = new BsonDocument
                {
                    ["_id"] = (int)type,
                    ["value"] = value
                };
                _collection.Upsert(doc);
            }
            public decimal GetAsDecimal(ProviderSettingsType type)
            {
                var data = _collection.FindById((int)type);
                return data != null ? data["value"].AsDecimal : default;
            }
            public void Set(ProviderSettingsType type, double value)
            {
                var doc = new BsonDocument
                {
                    ["_id"] = (int)type,
                    ["value"] = value
                };
                _collection.Upsert(doc);
            }
            public double GetAsDouble(ProviderSettingsType type)
            {
                var data = _collection.FindById((int)type);
                return data != null ? data["value"].AsDouble : default;
            }
            public void Set(ProviderSettingsType type, Guid value)
            {
                var doc = new BsonDocument
                {
                    ["_id"] = (int)type,
                    ["value"] = value
                };
                _collection.Upsert(doc);
            }
            public Guid GetAsGuid(ProviderSettingsType type)
            {
                var data = _collection.FindById((int)type);
                return data != null ? data["value"].AsGuid : default;
            }
            public void Set(ProviderSettingsType type, int value)
            {
                var doc = new BsonDocument
                {
                    ["_id"] = (int)type,
                    ["value"] = value
                };
                _collection.Upsert(doc);
            }
            public int GetAsInt32(ProviderSettingsType type)
            {
                var data = _collection.FindById((int)type);
                return data != null ? data["value"].AsInt32 : default;
            }
            public void Set(ProviderSettingsType type, long value)
            {
                var doc = new BsonDocument
                {
                    ["_id"] = (int)type,
                    ["value"] = value
                };
                _collection.Upsert(doc);
            }

            public long GetAsInt64(ProviderSettingsType type)
            {
                var data = _collection.FindById((int)type);
                return data != null ? data["value"].AsInt64 : 0L;
            }

            public void Set(ProviderSettingsType type, string value)
            {
                var doc = new BsonDocument
                {
                    ["_id"] = (int)type,
                    ["value"] = value
                };
                _collection.Upsert(doc);
            }

            public string? GetAsString(ProviderSettingsType type)
            {
                var data = _collection.FindById((int)type);
                return data?["value"].AsString;
            }

        }
        #endregion

        private readonly Framework _framework;
        private readonly LiteDatabase _database;
        private readonly bool _loadPositionOnly;
        private readonly IIdGenerator _idGen = new TickIdGen();
        private readonly ILiteCollection<ExecutionCommand> _commands;
        private readonly ILiteCollection<ExecutionReport> _reports;
        private readonly ILiteCollection<Stop> _stops;
        private readonly PositionStore _store;
        private readonly Logger _logger;

        public Action<ExecutionCommand> SetOrderId = _ => { };
        public string LastProviderId { get; private set; } = "0";

        private readonly Skyline.IdArray<StopStrategy> _stopStrategist = new(100);

        private void SetStopStrategy(Stop stop)
        {
            if (stop.Strategy is StopStrategy)
            {
                return;
            }

            var ss = _stopStrategist[stop.Strategy.Id];
            if (ss == null)
            {
                ss = new StopStrategy(_framework, stop.Strategy, string.Empty);
                _stopStrategist[stop.Strategy.Id] = ss;
            }
            stop.SetStrategy(ss);
        }

        public DatabaseOrderServer(
            Framework framework,
            LiteDatabase database,
            bool loadPositionOnly) : base(framework)
        {
            _logger = LogManager.GetLogger($"{framework.Name}_db");
            _framework = framework;
            _database = database;
            _loadPositionOnly = loadPositionOnly;
            _commands = _database.GetCollection<ExecutionCommand>("orders");
            _reports = _database.GetCollection<ExecutionReport>("reports");
            _stops = _database.GetCollection<Stop>("stops");
            Settings = new SettingsManager(_database.GetCollection<BsonDocument>("settings"));
            var positions = _database.GetCollection<PositionRecord>("positions");
            _store = new PositionStore(_framework, positions, _logger);
        }

        public SettingsManager Settings { get; }

        public PositionStore PositionStore => _store;

        public override void Save(ExecutionMessage message, int id = -1)
        {
            if (_framework.Mode == FrameworkMode.Simulation)
            {
                switch (message.TypeId)
                {
                    case DataObjectType.ExecutionCommand:
                        {
                            var cmd = (ExecutionCommand)message;
                            if (cmd.Type != ExecutionCommandType.Send)
                            {
                                break;
                            }
                            PositionFrozen(cmd.Order, false);
                        }
                        break;
                    case DataObjectType.ExecutionReport:
                        {
                            var report = (ExecutionReport)message;
                            _store.UpdatePosition(report, false);
                        }
                        break;
                }
                return;
            }

            try
            {
                _database.BeginTrans();
                switch (message.TypeId)
                {
                    case DataObjectType.ExecutionCommand:
                        {
                            var cmd = (ExecutionCommand)message;
                            if (cmd.Type != ExecutionCommandType.Send)
                            {
                                break;
                            }
                            var nextId = _idGen.Next().ToString();
                            cmd.Order.ClOrderId = nextId;
                            cmd.ClOrderId = nextId;
                            cmd.ProviderOrderId = nextId;
                            SetOrderId(cmd);
                            _commands.Insert(cmd);
                            PositionFrozen(cmd.Order);
                        }
                        break;
                    case DataObjectType.ExecutionReport:
                        {
                            var report = (ExecutionReport)message;
                            _reports.Insert(report);
                            _store.UpdatePosition(report);
                        }
                        break;
                }
                _database.Commit();
            }
            catch (Exception exception)
            {
                _logger.Debug(JsonConvert.SerializeObject(message, Formatting.Indented));
                _logger.Error(exception);
                _database.Rollback();
            }
        }

        public PositionRecord? PositionFrozen(Order order, bool saveToDatabase = true)
        {
            if (order.IsPositionFrozen())
            {
                return null;
            }
            var record = _store.UpdatePosition(order, saveToDatabase);
            order.SetPositionFrozen();
            return record;
        }

        public override List<ExecutionMessage> Load(string? series = null)
        {
            if (_framework.Mode == FrameworkMode.Simulation)
            {
                return new List<ExecutionMessage>();
            }

            //var subSideMap = new IdArray<SubSide>();
            var recordMap = new IdArray<List<ExecutionReport>>();
            var reports = _reports.FindAll().ToList();
            foreach (var report in reports)
            {
                var temp = recordMap[report.OrderId];
                if (temp == null)
                {
                    temp = new List<ExecutionReport>();
                    recordMap[report.OrderId] = temp;
                }
                temp.Add(report);
                //subSideMap[report.OrderId] = report.SubSide;
            }
            var commands = _commands.FindAll().ToList();
            foreach (var command in commands)
            {
                LastProviderId = command.ProviderOrderId;
                if (recordMap[command.OrderId] != null)
                {
                    command.ProviderOrderId = recordMap[command.OrderId][0].ProviderOrderId;
                }
                //SetSubSide(order, subSideMap[order.OrderId]);
            }

            if (_loadPositionOnly)
            {
                _store.LoadPosition();
                return new List<ExecutionMessage>();
            }
            PositionStore.Load(commands, recordMap);
            var list = new List<ExecutionMessage>();
            list.AddRange(commands);
            list.AddRange(reports);
            return list;
        }

        public HashSet<string> GetTrades(DateTime tradingDay)
        {
            var trades = new HashSet<string>();
            var result = _reports.Find(Query.GT("transactTime", tradingDay));
            foreach (var report in result)
            {
                trades.Add($"{report.ExecId}_{report.Side}");
            }
            return trades;
        }

        internal void SaveStop(Stop stop)
        {
            SetStopStrategy(stop);
            _stops.Upsert(stop);
        }

        internal void RemoveStop(Stop stop)
        {
            _stops.Delete(new ObjectId(stop.GetObjectId()));
        }

        internal void LoadStop(Strategy s)
        {
            ((DataEventMapper)_database.Mapper).StopSerializer.Strategy = s;
            foreach (var stop in _stops.Find(Query.EQ("strategyId", s.Id)))
            {
                s.AddStop(stop);
                SetStopStrategy(stop);
            }
        }

        public override void Close()
        {
            _database.Dispose();
        }
    }
}